package com.wzw.basics.concurrent.lock.reentrant;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 可重入锁
 * <p>
 * 可重入锁又名递归锁，是指在同一个线程在外层方法获取锁的时候，再进入该线程的的内层方法会自动获取锁（前提是锁对象得是同一个对象），不会因为之前已经获取过还没释放而阻塞。
 * <p>
 * Java中ReentrantLock和synchronized都是可重入锁，可重入锁的一个优点是可一定程度避免死锁。
 *
 * @author Wangzhiwen
 */
public class Reentrant {

    public static void main(String[] args) {
        SynchronizedReentrant.m1();
        SynchronizedReentrant.m2();

        LockReentrant.m1();
        LockReentrant.m2();
    }

}

/**
 * Synchronized 默认就是可以重入的锁，又称隐式可重入锁。
 * 原理解析：
 *      每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
 *      当执行monitorenter时，如果目标锁对象的计数器为零，那么说明它没有被其他线程所持有，Java虚拟机会将该锁对象的持有线程设置为当前线程，并且将其计数器加1。
 *      在目标锁对象的计数器不为零的情况下，如果锁对象的持有线程是当前线程，那么Java虚拟机可以将其计数器加1，否则需要等待，直至持有线程释放该锁。
 *      当执行monitorexit时，Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
 */
class SynchronizedReentrant {

    private static final Object lock = new Object();

    /**
     * 同步代码块可重入
     */
    public static void m1() {
        synchronized (lock) {
            System.out.println("Synchronized 代码块 可重入锁外层");
            synchronized (lock) {
                System.out.println("Synchronized 代码块 可重入锁中层");
                synchronized (lock) {
                    System.out.println("Synchronized 代码块 可重入锁内层");
                }
            }
        }
    }

    /**
     * 同步方法可重入
     */
    public synchronized static void m2() {
        System.out.println("Synchronized 方法 可重入锁外层");
        m3();
    }

    private synchronized static void m3() {
        System.out.println("Synchronized 方法 可重入锁中层");
        m4();
    }

    private synchronized static void m4() {
        System.out.println("Synchronized 方法 可重入锁内层");
    }

}

/**
 * ReentrantLock 实现Lock与AQS的可重入锁，需要手动加锁释放锁。又称显示锁
 * 原理解析：
 *      内部有一个int类型的状态计数器，重入时如果是当前持有锁的线程就加一。实现过程依靠AQS，显示加锁后必须显示解锁否则标识位不会置为0.锁就不会被释放
 */
class LockReentrant {
    // 非公平锁
    private static final Lock lock = new ReentrantLock(false);

    public static void m1() {
        // 第一次上锁
        lock.lock();
        System.out.println("ReentrantLock 同步块 外层");
        lock.lock();
        System.out.println("ReentrantLock 同步块 中层");
        lock.lock();
        System.out.println("ReentrantLock 同步块 内层");
        // 手动上锁几次就需要解锁几次，少了不行
        lock.unlock();
        lock.unlock();
        lock.unlock();
    }

    public static void m2() {
        lock.lock();
        System.out.println("ReentrantLock 同步方法 外层");
        m3();
        lock.unlock();
    }

    private static void m3() {
        lock.lock();
        System.out.println("ReentrantLock 同步方法 中层");
        m4();
        lock.unlock();
    }

    private static void m4() {
        lock.lock();
        System.out.println("ReentrantLock 同步方法 外层");
        lock.unlock();
    }
}
